#region Namespaces

using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;

#endregion

namespace Vibstudio.X4NET.Xml.Linq
{
    public abstract class XObject : IXmlLineInfo
    {
        private List<object> _annotations;

        internal XObject()
        {
        }

        public string BaseUri { get; internal set; }

        public XDocument Document
        {
            get
            {
                if (this is XDocument)
                {
                    return (XDocument)this;
                }

                for (XContainer e = Owner; e != null; e = e.Owner)
                {
                    if (e is XDocument)
                    {
                        return (XDocument)e;
                    }
                }
                return null;
            }
        }

        public abstract XmlNodeType NodeType { get; }

        public XElement Parent
        {
            get { return Owner as XElement; }
        }

        internal XContainer Owner { get; private set; }

        internal int LineNumber { get; set; }

        internal int LinePosition { get; set; }

        int IXmlLineInfo.LineNumber
        {
            get { return LineNumber; }
        }

        int IXmlLineInfo.LinePosition
        {
            get { return LinePosition; }
        }

        bool IXmlLineInfo.HasLineInfo()
        {
            return LineNumber > 0;
        }

        public event EventHandler<XObjectChangeEventArgs> Changing;

        public event EventHandler<XObjectChangeEventArgs> Changed;

        internal void SetOwner(XContainer node)
        {
            Owner = node;
        }

        public void AddAnnotation(object annotation)
        {
            if (annotation == null)
            {
                throw new ArgumentNullException("annotation");
            }
            if (_annotations == null)
            {
                _annotations = new List<object>();
            }
            _annotations.Add(annotation);
        }

        public T Annotation<T>() where T : class
        {
            return (T)Annotation(typeof(T));
        }

        public object Annotation(Type type)
        {
            return Annotations(type).FirstOrDefault();
        }

        public IEnumerable<T> Annotations<T>() where T : class
        {
            foreach (T o in Annotations(typeof(T)))
            {
                yield return o;
            }
        }

        public IEnumerable<object> Annotations(Type type)
        {
            if (type == null)
            {
                throw new ArgumentNullException("type");
            }
            if (_annotations == null)
            {
                yield break;
            }
            foreach (object o in _annotations)
            {
                if (type.IsAssignableFrom(o.GetType()))
                {
                    yield return o;
                }
            }
        }

        public void RemoveAnnotations<T>() where T : class
        {
            RemoveAnnotations(typeof(T));
        }

        public void RemoveAnnotations(Type type)
        {
            if (_annotations == null)
            {
                return;
            }
            for (int i = 0; i < _annotations.Count; i++)
            {
                if (type.IsAssignableFrom(_annotations[i].GetType()))
                {
                    _annotations.RemoveAt(i);
                }
            }
        }

        internal void FillLineInfoAndBaseUri(XmlReader r, LoadOptions options)
        {
            if ((options & LoadOptions.SetLineInfo) != LoadOptions.None)
            {
                IXmlLineInfo li = r as IXmlLineInfo;
                if (li != null && li.HasLineInfo())
                {
                    LineNumber = li.LineNumber;
                    LinePosition = li.LinePosition;
                }
            }
            if ((options & LoadOptions.SetBaseUri) != LoadOptions.None)
            {
                BaseUri = r.BaseURI;
            }
        }

        internal void OnAddingObject(object addedObject)
        {
            OnChanging(addedObject, XObjectChangeEventArgs.Add);
        }

        internal void OnAddedObject(object addedObject)
        {
            OnChanged(addedObject, XObjectChangeEventArgs.Add);
        }

        internal void OnNameChanging(object renamedObject)
        {
            OnChanging(renamedObject, XObjectChangeEventArgs.Name);
        }

        internal void OnNameChanged(object renamedObject)
        {
            OnChanged(renamedObject, XObjectChangeEventArgs.Name);
        }

        internal void OnRemovingObject(object removedObject)
        {
            OnChanging(removedObject, XObjectChangeEventArgs.Remove);
        }

        internal void OnRemovedObject(object removedObject)
        {
            OnChanged(removedObject, XObjectChangeEventArgs.Remove);
        }

        internal void OnValueChanging(object changedObject)
        {
            OnChanging(changedObject, XObjectChangeEventArgs.Value);
        }

        internal void OnValueChanged(object changedObject)
        {
            OnChanged(changedObject, XObjectChangeEventArgs.Value);
        }

        private void OnChanging(object sender, XObjectChangeEventArgs args)
        {
            if (Changing != null)
            {
                Changing(sender, args);
            }
            if (Owner != null)
            {
                Owner.OnChanging(sender, args);
            }
        }

        private void OnChanged(object sender, XObjectChangeEventArgs args)
        {
            if (Changed != null)
            {
                Changed(sender, args);
            }
            if (Owner != null)
            {
                Owner.OnChanged(sender, args);
            }
        }
    }
}